package com.sgj.gulimall.order.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.sgj.common.constant.OrderConstant;
import com.sgj.common.constant.RabbitConstant;
import com.sgj.common.constant.RedisConstant;
import com.sgj.common.enums.BizCodeEnum;
import com.sgj.common.to.mq.OrderTo;
import com.sgj.common.utils.R;
import com.sgj.common.vo.*;
import com.sgj.gulimall.order.entity.OrderItemEntity;
import com.sgj.gulimall.order.enums.OrderStatusEnum;
import com.sgj.gulimall.order.feign.CartFeignService;
import com.sgj.gulimall.order.feign.MemberFeignService;
import com.sgj.gulimall.order.feign.ProductFeignService;
import com.sgj.gulimall.order.feign.WmsFeignService;
import com.sgj.gulimall.order.interceptor.LoginUserInterceptor;
import com.sgj.gulimall.order.service.OrderItemService;
import com.sgj.gulimall.order.to.OrderCreateTo;
import com.sgj.common.vo.OrderConfirmVo.*;
import com.sgj.gulimall.order.vo.OrderSubmitVo;
import com.sgj.gulimall.order.vo.SpuInfoVo;
import com.sgj.gulimall.order.vo.SubmitOrderResponseVo;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sgj.common.utils.PageUtils;
import com.sgj.common.utils.Query;

import com.sgj.gulimall.order.dao.OrderDao;
import com.sgj.gulimall.order.entity.OrderEntity;
import com.sgj.gulimall.order.service.OrderService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;


@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {

    private ThreadLocal<OrderSubmitVo> submitVoThreadLocal = new ThreadLocal<>();

    // 优惠
    private static final BigDecimal DISCOUNT = BigDecimal.ZERO;

    @Autowired
    private MemberFeignService memberFeignService;
    @Autowired
    private CartFeignService cartFeignService;
    @Autowired
    private WmsFeignService wmsFeignService;
    @Autowired
    private ProductFeignService productFeignService;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ThreadPoolExecutor executor;
    @Autowired
    private OrderItemService orderItemService;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {

        OrderConfirmVo vo = new OrderConfirmVo();

        // 从拦截器中获取当前用户
        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();

        // 获取请求头数据
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 远程查询获取用户地址信息
        CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
            // 异步请求丢失请求头，重新赋值
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
            vo.setAddress(address);
        }, executor);

        // 远程查询购物车内所有选中的购物项
        CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
            vo.setItems(items);
        }, executor).thenRunAsync(() -> {
            List<OrderItemVo> items = vo.getItems();
            List<Long> idList = items.stream().map(OrderItemVo::getSkuId).collect(Collectors.toList());
            R hasStock = wmsFeignService.getSkusHasStock(idList);
            List<SkuHasStockVo> stockVoList = hasStock.getData(new TypeReference<List<SkuHasStockVo>>() {});
            if (!CollectionUtils.isEmpty(stockVoList)) {
                Map<Long, Boolean> stocks = stockVoList.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock));
                vo.setStocks(stocks);
            }
        }, executor);

        // 当前用户积分信息
        vo.setIntegration(memberRespVo.getIntegration());

        // 防重令牌
        String token = UUID.randomUUID().toString().replace("-", "");
        vo.setOrderToken(token);
        redisTemplate.opsForValue()
                .set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId(), token, 15, TimeUnit.MINUTES);

        // 等待异步任务全部执行完成
        CompletableFuture.allOf(getAddressFuture, cartFuture).get();

        // 价格在实体类中自动计算，无需获取
        return vo;
    }

    // @GlobalTransactional
    @Transactional
    @Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {
        // 将所需数据存入线程
        submitVoThreadLocal.set(vo);
        SubmitOrderResponseVo responseVo = new SubmitOrderResponseVo();
        responseVo.setCode(BizCodeEnum.ORDER_PASS.getCode());
        // 从拦截器中获取当前用户
        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
        // 验证令牌
        String orderToken = vo.getOrderToken();
        // 原子验证令牌和删除令牌(0失败，1成功)
        Long result = redisTemplate.execute(new DefaultRedisScript<Long>(RedisConstant.SCRIPT, Long.class),
                Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()), orderToken);
        // 令牌验证成功
        if (result == 1L) {
            // 下单流程：创建订单，验证价格，锁定库存
            // 创建订单
            OrderCreateTo order = createOrder();
            // 价格校验
            BigDecimal payAmount = order.getOrder().getPayAmount();
            BigDecimal payPrice = vo.getPayPrice();
            // 金额对比(差价在0.01内都是可接受范围)
            if (Math.abs(payAmount.subtract(payPrice).doubleValue()) < 0.01) {
                // 保存订单
                saveOrder(order);
                // 库存锁定
                WareSkuLockVo lockVo = new WareSkuLockVo();
                lockVo.setOrderSn(order.getOrder().getOrderSn());
                List<OrderItemVo> locks = order.getOrderItems().stream().map(item -> {
                    OrderItemVo orderItemVo = new OrderItemVo();
                    orderItemVo.setSkuId(item.getSkuId());
                    orderItemVo.setCount(item.getSkuQuantity());
                    orderItemVo.setTitle(item.getSkuName());
                    return orderItemVo;
                }).collect(Collectors.toList());
                lockVo.setLocks(locks);
                // 远程锁定库存
                R r = wmsFeignService.orderLockStock(lockVo);
                if (r.getCode() == 0) {
                    // 库存锁定成功
                    responseVo.setOrder(order.getOrder());
                    // int i = 10 / 0;
                    // 订单创建成功，发送消息给MQ
                    rabbitTemplate.convertAndSend(RabbitConstant.ORDER_EXCHANGE, RabbitConstant.ROUTING_KEY_ORDER_CREATE, order.getOrder());
                    return responseVo;
                } else {
                    // 库存锁定失败
                    responseVo.setCode(BizCodeEnum.NO_STOCK_EXCEPTION.getCode());
                    return responseVo;
                }
            }
        }
        // 令牌验证失败
        responseVo.setCode(BizCodeEnum.TOKEN_VAILD_EXCEPTION.getCode());
        return responseVo;
    }

    @Override
    public OrderEntity getOrderByOrderSn(String orderSn) {
        return baseMapper.selectOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));
    }

    @Override
    public void closeOrder(OrderEntity entity) {
        // 查询当前订单最新状态
        OrderEntity orderEntity = baseMapper.selectById(entity.getId());
        if (OrderStatusEnum.CREATE_NEW.getCode() == orderEntity.getStatus()) {
            // 关单
            OrderEntity updateEntity = OrderEntity.builder()
                    .id(entity.getId())
                    .status(OrderStatusEnum.CANCLED.getCode())
                    .build();
            baseMapper.updateById(updateEntity);

            OrderTo orderTo = new OrderTo();
            BeanUtils.copyProperties(orderEntity, orderTo);
            // 发送消息
            try {
                // TODO 保证消息一定能发出，每一条消息都做日志记录，保存到数据库中
                rabbitTemplate.convertAndSend(RabbitConstant.ORDER_EXCHANGE, RabbitConstant.ROUTING_KEY_ORDER_RELEASE_OTHER, orderTo);
            } catch (Exception e) {
                // TODO 将没发送成功的消息进行重试发送
            }
        }
    }

    /**
     * 保存订单
     * @param order
     */
    public void saveOrder(OrderCreateTo order) {
        // 保存订单
        OrderEntity entity = order.getOrder();
        entity.setModifyTime(new Date());
        this.save(entity);

        // 保存订单项
        List<OrderItemEntity> orderItems = order.getOrderItems();
        orderItemService.saveBatch(orderItems);
    }

    /**
     * 创建订单
     * @return
     */
    private OrderCreateTo createOrder() {
        // 生成订单号
        String orderSn = IdWorker.getTimeId();
        // 创建订单
        OrderEntity orderEntity = buildOrder(orderSn);
        // 获取所有订单项
        List<OrderItemEntity> itemEntities = buildOrderItems(orderSn);
        // 计算价格
        computePrice(orderEntity, itemEntities);

        OrderCreateTo createTo = OrderCreateTo.builder()
                .order(orderEntity)
                .fare(orderEntity.getFreightAmount())
                .orderItems(itemEntities)
                .build();
        return createTo;
    }

    private void computePrice(OrderEntity orderEntity, List<OrderItemEntity> itemEntities) {
        // 应付总价
        BigDecimal totalPrice = itemEntities.stream()
                .filter(item -> Objects.nonNull(item.getRealAmount()))
                .map(OrderItemEntity::getRealAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // 优惠券金额
        BigDecimal couponPrice = itemEntities.stream()
                .filter(item -> Objects.nonNull(item.getCouponAmount()))
                .map(OrderItemEntity::getCouponAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // 积分抵扣金额
        BigDecimal integrationPrice = itemEntities.stream()
                .filter(item -> Objects.nonNull(item.getIntegrationAmount()))
                .map(OrderItemEntity::getIntegrationAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // 折扣优惠金额
        BigDecimal promotionPrice = itemEntities.stream()
                .filter(item -> Objects.nonNull(item.getPromotionAmount()))
                .map(OrderItemEntity::getPromotionAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // 订单获取积分
        Integer integration = itemEntities.stream()
                .filter(item -> Objects.nonNull(item.getGiftIntegration()))
                .mapToInt(OrderItemEntity::getGiftIntegration)
                .sum();
        // 订单获取成长值
        Integer giftGrowth = itemEntities.stream()
                .filter(item -> Objects.nonNull(item.getGiftGrowth()))
                .mapToInt(OrderItemEntity::getGiftGrowth)
                .sum();

        // 订单总价
        orderEntity.setTotalAmount(totalPrice);
        // 应付总价
        orderEntity.setPayAmount(totalPrice.add(orderEntity.getFreightAmount()));
        // 优惠券优惠
        orderEntity.setCouponAmount(couponPrice);
        // 积分优惠
        orderEntity.setIntegrationAmount(integrationPrice);
        // 折扣优惠
        orderEntity.setPromotionAmount(promotionPrice);
        // 订单积分
        orderEntity.setIntegration(integration);
        // 订单成长值
        orderEntity.setGrowth(giftGrowth);
        // 订单是否被删除(0：未删除，1：已删除)
        orderEntity.setDeleteStatus(0);
    }

    /**
     * 构建订单
     * @param orderSn
     * @return
     */
    private OrderEntity buildOrder(String orderSn) {
        // 获取当前用户信息
        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
        // 获取收货地址信息
        OrderSubmitVo submitVo = submitVoThreadLocal.get();
        R r = wmsFeignService.getFare(submitVo.getAddrId());
        FareVo fareResp = r.getData(new TypeReference<FareVo>() {});

        OrderEntity order = OrderEntity.builder()
                // 订单号
                .orderSn(orderSn)
                // 用户信息
                .memberId(memberRespVo.getId())
                .memberUsername(memberRespVo.getUsername())
                // 运费
                .freightAmount(fareResp.getFare())
                // 收货人信息
                .receiverCity(fareResp.getAddress().getCity())
                .receiverDetailAddress(fareResp.getAddress().getDetailAddress())
                .receiverName(fareResp.getAddress().getName())
                .receiverPhone(fareResp.getAddress().getPhone())
                .receiverPostCode(fareResp.getAddress().getPostCode())
                .receiverProvince(fareResp.getAddress().getProvince())
                .receiverRegion(fareResp.getAddress().getRegion())
                // 订单相关状态信息
                .status(OrderStatusEnum.CREATE_NEW.getCode())
                .autoConfirmDay(OrderConstant.ORDER_AUTO_CONFIRM_DAYS)
                .build();
        return order;
    }

    /**
     * 构建所有订单项数据
     * @param orderSn
     * @return
     */
    private List<OrderItemEntity> buildOrderItems(String orderSn) {
        List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
        if (!CollectionUtils.isEmpty(items)) {
            List<OrderItemEntity> entityList = items.stream()
                    .map(cartItem -> {
                        OrderItemEntity entity = buildOrderItem(cartItem);
                        entity.setOrderSn(orderSn);
                        return entity;
                    })
                    .collect(Collectors.toList());
            return entityList;
        }
        return null;
    }

    /**
     * 构建单个订单项
     * @param vo
     * @return
     */
    private OrderItemEntity buildOrderItem(OrderItemVo vo) {
        // 商品信息
        R r = productFeignService.getSpuInfoBySkuId(vo.getSkuId());
        SpuInfoVo spuInfoVo = r.getData(new TypeReference<SpuInfoVo>() {});

        // 原价
        BigDecimal orign = vo.getPrice().multiply(new BigDecimal(vo.getCount().toString()));
        // 实际金额(去掉三个优惠金额，这里暂时写死)
        BigDecimal realAmount = orign.subtract(DISCOUNT).subtract(DISCOUNT).subtract(DISCOUNT);

        OrderItemEntity entity = OrderItemEntity.builder()
                // 商品信息
                .spuId(spuInfoVo.getId())
                .spuBrand(spuInfoVo.getBrandId().toString())
                .spuName(spuInfoVo.getSpuName())
                .categoryId(spuInfoVo.getCatalogId())
                // sku信息
                .skuId(vo.getSkuId())
                .skuName(vo.getTitle())
                .skuPic(vo.getImage())
                .skuPrice(vo.getPrice())
                .skuAttrsVals(StringUtils.collectionToDelimitedString(vo.getSkuAttr(), ";"))
                .skuQuantity(vo.getCount())
                // 积分信息
                .giftGrowth(vo.getPrice().multiply(new BigDecimal(vo.getCount().toString())).intValue())
                .giftIntegration(vo.getPrice().multiply(new BigDecimal(vo.getCount().toString())).intValue())
                // 订单项价格信息
                .promotionAmount(DISCOUNT)
                .couponAmount(DISCOUNT)
                .integrationAmount(DISCOUNT)
                // 当前订单项实际金额
                .realAmount(realAmount)
                .build();
        return entity;
    }
}